unit smLuaUtils;

interface

uses
  Gen2, Lua, SysUtils, smExceptions, smConst;

  function InitScripting(): Boolean;
  function UninitScripting(): Boolean;
  function RunScript(const ACmd: AnsiString): Boolean;
  function RunInitFile(const ASceneName: AnsiString): Boolean;
  function StopInitFile(): Boolean;

  // Just metatable
  procedure SetInterface(const TableName: AnsiString;
      const Functions: Pointer);
  procedure SetObject(const AObject: Pointer; const AInterface: AnsiString);
  procedure SetClass(const ClassName: AnsiString;
      const Functions: Pointer);
  procedure SetFieldInt(const ClassName: AnsiString;
      const FieldName: AnsiString; const Value: Integer);
  procedure SetFieldString(const ClassName, FieldName, Value: AnsiString);

  // General event handlers
  // each of these routines call global systemHANDLER_NAME script
  //  procedure, and it descends to SCENE_DEPENDENT handler
  procedure handlerOnLoad();
  procedure handlerOnTime();
  procedure handlerOnUnload();

implementation

uses
  smPlayer, smClassGame, smClassGUI, smClassPlayer, smClassWorld;

var
  LuaState: lua_State;

procedure SetDefaultSceneLoaders();
begin
  lua_getfield(LuaState, LUA_GLOBALSINDEX, PAnsiChar(LuaActiveScene));
  lua_getfield(LuaState, LUA_GLOBALSINDEX, PAnsiChar(DefaultSceneLoadName));
  lua_setfield(LuaState, -2, PAnsiChar(LuaInitialize));
  lua_getfield(LuaState, LUA_GLOBALSINDEX, PAnsiChar(DefaultSceneUnLoadName));
  lua_setfield(LuaState, -2, PAnsiChar(LuaFinalize));
  lua_remove(LuaState, -1);
end;

function InitScripting;
begin
  UninitScripting();
  if LoadLuaLib() <> 0 then
  begin
    LuaState := lua_open();

    try
      luaopen_base(LuaState);

      smClassGame.OpenClass(LuaState);
      smClassGUI.OpenClass(LuaState);
      smClassPlayer.OpenClass(LuaState);
      smClassWorld.OpenClass(LuaState);

      luaL_dostring(LuaState, PAnsiChar(DefaultSceneLoadFunction));
      luaL_dostring(LuaState, PAnsiChar(DefaultSceneUnloadFunction));

      // ActiveScene global
      lua_newtable(LuaState);
      lua_setglobal(LuaState, PAnsiChar(LuaActiveScene));

      // Default fields
      SetDefaultSceneLoaders();
    except
      raise ELuaInitialize.Create(LuaState);
    end;
  end;

  Result := LuaState <> nil;
end;

function UninitScripting;
begin
  if LuaState <> nil then
  begin
    lua_gc(LuaState, LUA_GCCOLLECT, 0);
    lua_close(LuaState);
    LuaState := nil;
  end;

  Result := LuaState = nil;
end;

function RunScript;
begin
  if Length(ACmd) > 0 then
    try
      if luaL_dostring(LuaState, PAnsiChar(ACmd)) = 0 then
        Result := True
      else
        raise ELuaException.Create(LuaState, 'runscript error');
    except
      Result := False;
    end

  else
    Result := False;
end;

function RunInitFile;
var
  LoadRes: Integer;
begin
  try
    // todo: Load from PE-resource
    LoadRes :=
      luaL_dofile(LuaState, PAnsiChar('core\' + ASceneName + '.lua'));
    Result := LoadRes = 0;

    if Result then
    begin
      lua_getfield(LuaState, LUA_GLOBALSINDEX, PAnsiChar(LuaActiveScene));
      lua_getfield(LuaState, -1, PAnsiChar(LuaInitialize));
      lua_remove(LuaState, -2);
      // no arguments required
      if lua_pcall(LuaState, 0, 0, 0) <> 0 then
        raise ELuaInitfile.Create(LuaState, 'calling Initialize proc');
    end
    else
      raise ELuaInitfile.CreateFmt(
        LuaState, 'error loading initfile: %d', [LoadRes]
      );

  except
    Result := False;
  end;
end;

function StopInitFile;
begin
  try
    lua_getfield(LuaState, LUA_GLOBALSINDEX, PAnsiChar(LuaActiveScene));
    lua_getfield(LuaState, -1, PAnsiChar(LuaFinalize));
    lua_remove(LuaState, -2);
    if lua_pcall(LuaState, 0, 0, 0) <> 0 then
      raise ELuaInitfileStop.Create(LuaState, 'calling Finalize proc');

    // Set defaults
    SetDefaultSceneLoaders();
    lua_gc(LuaState, LUA_GCCOLLECT, 0);

    Result := True;
  except
    Result := False;
  end;
end;

procedure SetInterface;
begin
  // Create new metatable ("Class")
  luaL_newmetatable(LuaState, PAnsiChar(TableName));
  lua_pushstring(LuaState, '__index');
  lua_pushvalue(LuaState, -2);
  lua_settable(LuaState, -3);

  // Register given functions
  luaL_register(LuaState, nil, Functions);
end;

procedure SetObject;
begin
  if Assigned(AObject) then
  begin
    PCardinal(lua_newuserdata(LuaState, SizeOf(Pointer)))^ := Cardinal(AObject);

    // Set metatable for new created lua object
    luaL_getmetatable(LuaState, PAnsiChar(AInterface));
    lua_setmetatable(LuaState, -2);
  end;
end;

procedure SetClass;
begin
  // Just add new global with assigned functions
  luaL_register(LuaState, PAnsiChar(ClassName), Functions);
end;

procedure SetFieldInt;
begin
  lua_getfield(LuaState, LUA_GLOBALSINDEX, PAnsiChar(ClassName));
  lua_pushinteger(LuaState, Value);
  lua_setfield(LuaState, -2, PAnsiChar(FieldName));

  // Normalize stack
  lua_remove(LuaState, -1);
end;

procedure SetFieldString;
begin
  lua_getfield(LuaState, LUA_GLOBALSINDEX, PAnsiChar(ClassName));
  lua_pushstring(LuaState, PAnsiChar(Value));
  lua_setfield(LuaState, -2, PAnsiChar(FieldName));

  // Normalize stack
  lua_remove(LuaState, -1);
end;

procedure handlerOnLoad();
begin
  // Occures ones the location is loaded
  lua_getfield(LuaState, LUA_GLOBALSINDEX, PAnsiChar(LuaActiveScene));
  lua_getfield(LuaState, -1, PAnsiChar(LuaOnLoad));
  lua_remove(LuaState, -2);

  if lua_isfunction(LuaState, -1) then
    try
      // Trying to call systemOnLoad handler
      if lua_pcall(LuaState, 0, 0, 0) <> 0 then
        raise ELuaException.Create(LuaState, 'OnLoad event');
    except
      // already done in exception
    end

  else
    lua_remove(LuaState, -1);

end;

procedure handlerOnTime;
begin
  // Find the function
  lua_getfield(LuaState, LUA_GLOBALSINDEX, PAnsiChar(LuaActiveScene));
  lua_getfield(LuaState, -1, PAnsiChar(LuaOnTime));
  lua_remove(LuaState, -2);

  if lua_isfunction(LuaState, -1) then
  begin
    // Push arguments
    with Player.World.Time^ do
    begin
      lua_pushinteger(LuaState, Year);
      lua_pushinteger(LuaState, Month);
      lua_pushinteger(LuaState, Day);
      lua_pushinteger(LuaState, Time);
    end;

    try
      if lua_pcall(LuaState, 4, 0, 0) <> 0 then
        raise ELuaException.Create(LuaState, 'OnTime event');
    except
      // already is done
    end;

  end
  else
    lua_remove(LuaState, -1);

end;

procedure handlerOnUnload;
begin
  // Occures ones before location is unloaded
  lua_getfield(LuaState, LUA_GLOBALSINDEX, PAnsiChar(LuaActiveScene));
  lua_getfield(LuaState, -1, PAnsiChar(LuaOnUnload));
  lua_remove(LuaState, -2);

  if lua_isfunction(LuaState, -1) then
    try
      // Trying to call systemOnLoad handler
      if lua_pcall(LuaState, 0, 0, 0) <> 0 then
        raise ELuaException.Create(LuaState, 'OnUnload event');
    except
      // already done in exception
    end

  else
    lua_remove(LuaState, -1);

end;

Initialization
  LuaState := nil;

end.
